use std::{collections::HashMap, fmt::Display, fs::File, io, ops::Drop};

use chrono::prelude::*;
use serde::{Deserialize, Serialize};
use serde_json::{from_reader, to_writer};

// todo: 使用更合理的方式， ~不能用。。。
const DATA_PATH: &str = "C:/Users/youz/.life/data.json";

#[derive(Deserialize, Serialize, Clone)]
pub struct Event {
    pub name: String,
    pub alias: Option<String>,
    pub point: i32,
}
pub type Task = Event;

#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Date {
    pub year: i32,
    pub month: u32,
    pub day: u32,
}

impl Display for Date {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}-{}-{}", self.year, self.month, self.day)
    }
}

impl Date {
    fn now() -> Self {
        let now = Local::now();
        Date {
            day: now.day(),
            month: now.month(),
            year: now.year(),
        }
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct History {
    pub date: Date,
    pub name: String,
    pub point: i32,
    pub event_type: String,
}

#[derive(Deserialize, Serialize, Default)]
pub struct Manager {
    point: i32,
    daily_tasks: Vec<Task>,                // 每日刷新
    special_tasks: Vec<Task>,              // 一般只可以完成一次
    special_events: Vec<Event>, // 可以多次完成, 有扣分项, 上面两个虽然 point 类型用的 i32, 但是一般大于0
    daily_finished: HashMap<String, Date>, // 名称和最新完成日期
    history: Vec<History>,
}

enum FindedEvent {
    SE(usize),
    ST(usize),
    DT(usize),
}

impl Manager {
    pub fn new() -> Self {
        let file = File::open(DATA_PATH);
        if let Err(err) = file {
            if err.kind() == io::ErrorKind::NotFound {
                return Self {
                    point: 0,
                    daily_tasks: Vec::new(),
                    special_tasks: Vec::new(),
                    special_events: Vec::new(),
                    daily_finished: HashMap::new(),
                    history: Vec::new(),
                };
            } else {
                panic!("can not open existed data file {DATA_PATH}");
            }
        }
        let reader = file.unwrap();
        from_reader(reader).expect("data can't convert to life manager")
    }

    pub fn point(&self) -> i32 {
        self.point
    }

    pub fn get_daily_tasks(&self) -> &Vec<Task> {
        &self.daily_tasks
    }

    pub fn get_special_tasks(&self) -> &Vec<Task> {
        &self.special_tasks
    }

    pub fn get_special_events(&self) -> &Vec<Task> {
        &self.special_events
    }

    pub fn add_daily_task(&mut self, name: String, point: i32, alias: Option<String>) {
        // todo: check name exist
        self.daily_tasks.push(Task { name, point, alias });
    }

    pub fn add_special_task(&mut self, name: String, point: i32, alias: Option<String>) {
        // todo: check name exist
        self.special_tasks.push(Task { name, point, alias });
    }

    pub fn add_special_event(&mut self, name: String, point: i32, alias: Option<String>) {
        // todo: check name exist
        self.special_events.push(Task { name, point, alias });
    }

    /// return new point
    pub fn use_point_with_descript(&mut self, point: i32, desc: String) -> i32 {
        self.point -= point;
        self.history.push(History {
            date: Date::now(),
            event_type: "use".into(),
            point: -point,
            name: desc,
        });
        self.point
    }

    pub fn remove(&mut self, name: &str) -> Option<Event> {
        use FindedEvent::*;
        if let Some(res) = self.find_event_by_name(name) {
            Some(match res {
                DT(i) => self.daily_tasks.remove(i),
                ST(i) => self.special_tasks.remove(i),
                SE(i) => self.special_events.remove(i),
            })
        } else {
            None
        }
    }

    /// return new point
    pub fn play(&mut self, name: &str) -> Option<i32> {
        use FindedEvent::*;
        if let Some(res) = self.find_event_by_name(name) {
            match res {
                DT(i) => {
                    let task = self.daily_tasks[i].clone();
                    // todo: 如果今日已完成，返回特殊值
                    self.update_daily_task_info(&task);
                    self.add_history(&task, "daily");
                }
                ST(i) => {
                    let task = self.special_tasks[i].clone();

                    self.point += task.point;
                    self.special_tasks.remove(i);
                    self.add_history(&task, "special");
                }
                SE(i) => {
                    let event = self.special_events[i].clone();

                    self.point += event.point;
                    self.add_history(&event, "event");
                }
            }
            Some(self.point)
        } else {
            None
        }
    }

    pub fn get_history(&self) -> &Vec<History> {
        &self.history
    }

    pub fn luck_draw(&mut self) -> i32 {
        let point = (rand::random::<u16>() % 30) as i32;
        self.point += point;
        self.history.push(History {
            date: Date::now(),
            event_type: "luck draw".into(),
            name: "luck draw".into(),
            point,
        });
        self.point
    }

    fn update_daily_task_info(&mut self, task: &Task) {
        let info = self.daily_finished.get_mut(&task.name);
        let now = Date::now();
        if let Some(date) = info {
            if now > *date {
                *date = now;
                self.point += task.point;
            }
        } else {
            self.daily_finished.insert(task.name.to_string(), now);
            self.point += task.point;
        }
    }

    fn add_history(&mut self, event: &Event, event_type: &str) {
        self.history.push(History {
            name: event.name.clone(),
            event_type: event_type.into(),
            date: Date::now(),
            point: event.point,
        })
    }

    fn find_event_by_name(&self, name: &str) -> Option<FindedEvent> {
        macro_rules! find {
            ($list: expr, $ty: path) => {{
                let result = $list
                    .iter()
                    .enumerate()
                    .find(|(_, e)| {
                        e.name == name || (e.alias.is_some() && e.alias.as_ref().unwrap() == name)
                    })
                    .map(|(i, _)| $ty(i));
                if result.is_some() {
                    return result;
                }
            }};
        }
        find!(self.daily_tasks, FindedEvent::DT);
        find!(self.special_tasks, FindedEvent::ST);
        find!(self.special_events, FindedEvent::SE);
        None
    }
}

impl Drop for Manager {
    fn drop(&mut self) {
        // todo: 如果 .life 目录不存在，会报错，所以增加创建父级目录的代码
        let writer = File::create(DATA_PATH).unwrap();
        to_writer(writer, self).unwrap();
    }
}
